<template>
  <div class="border border-gray-300 select-none w-[900px] !text-[12px]">
    <!-- Header Row -->
    <div class="header grid grid-cols-[60px_1fr_60px] items-center border-b border-gray-200 bg-gray-50 text-sm font-semibold">
      <div class="p-2 text-center border-r border-gray-300 h-full flex items-center justify-center">时间</div>
      <div class="timeline-header relative h-10 px-1 border-r border-gray-200">
        <!-- Hour Labels & Ticks -->
        <template v-for="hour in 25">
          <div
            class="hour-tick absolute top-0 bottom-0 w-px bg-gray-300 pointer-events-none"
            :style="{ left: `${(hour -1) / 24 * 100}%` }"
          ></div>
          <div
            v-if="(hour-1) % 2 === 0 && hour <= 25"
            :key="'label-' + hour"
            class="hour-label absolute bottom-0 text-xs text-gray-500 transform -translate-x-1/2 pointer-events-none"
            :style="{ left: `${(hour -1) / 24 * 100}%` }"
          >
            {{ String(hour-1).padStart(2, '0') }}
          </div>
        </template>
      </div>
      <!-- Header Actions: Delete All -->
      <div class="p-2 text-center h-full flex items-center justify-center">
        <button
          @click="clearAllSchedule"
          title="清空所有时间段"
          class="text-red-600 hover:text-red-800 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
          :disabled="isScheduleEmpty"
        >
          <!-- Trash Icon -->
          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
            <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
          </svg>
        </button>
      </div>
    </div>

    <!-- Day Rows -->
    <div
      v-for="(dayName, dayIndex) in daysOfWeek"
      :key="dayIndex"
      class="day-row grid grid-cols-[60px_1fr_60px] items-center border-b border-gray-200 last:border-b-0"
      :class="{'bg-gray-50': dragging || resizing}"
    >
      <!-- Day Label -->
      <div class="p-1 text-center border-r border-gray-200 text-sm font-medium bg-gray-50 h-full flex items-center justify-center">
        {{ dayName }}
      </div>

      <!-- Timeline -->
      <div
        ref="timelines"
        class="timeline relative h-8 bg-white cursor-crosshair overflow-hidden border-r border-gray-200"
        :data-day-index="dayIndex"
        @mousedown="handleTimelineMouseDown($event, dayIndex)"
      >
        <!-- Background Lines -->
<!--        <div class="absolute inset-0 flex pointer-events-none">-->
<!--          <div v-for="i in 48" :key="'line-'+i" class="w-full h-full border-l border-gray-100 first:border-l-0"></div>-->
<!--        </div>-->
        <div class="absolute inset-0 flex pointer-events-none">
          <div v-for="i in 24" :key="'hour-line-'+i" class="w-full h-full border-l border-gray-300 first:border-l-0"></div>
        </div>

        <!-- Existing Selection Bars -->
        <div
          v-for="(range, rangeIndex) in internalSchedule[dayIndex]"
          :key="`${dayIndex}-${range.id}`"
          ref="selectionBars"
          :data-bar-id="`${dayIndex}-${range.id}`"
          class="selection-bar group absolute top-0 bottom-0
          bg-blue-600 bg-opacity-75 hover:bg-opacity-100  cursor-move
           flex items-center justify-between"
          :style="getBarStyle(range)"
          @mouseover="handleBarMouseOver($event, dayIndex, rangeIndex)"
          @mousedown.stop="handleBarMouseDown($event, dayIndex, rangeIndex)"
          @mouseout="hideTooltip"
          @click="handleBarClick($event, dayIndex, rangeIndex)"
        >
          <!-- Resize Handles -->
          <div
            class="resize-handle left-handle h-full w-2 cursor-ew-resize opacity-0 group-hover:opacity-100 bg-blue-800 bg-opacity-50 hover:bg-opacity-75"
            :data-day-index="dayIndex" :data-range-index="rangeIndex" :data-bar-id="`${dayIndex}-${range.id}`"
            @mousedown.stop="handleResizeMouseDown($event, dayIndex, rangeIndex, 'left')"
          ></div>
          <div
            class="resize-handle right-handle h-full w-2 cursor-ew-resize opacity-0 group-hover:opacity-100 bg-blue-800 bg-opacity-50 hover:bg-opacity-75"
            :data-day-index="dayIndex" :data-range-index="rangeIndex" :data-bar-id="`${dayIndex}-${range.id}`"
            @mousedown.stop="handleResizeMouseDown($event, dayIndex, rangeIndex, 'right')"
          ></div>
        </div>

        <!-- Temporary Drag Selection Visual -->
        <div
          v-if="dragging && draggingInfo.dayIndex === dayIndex"
          ref="dragSelection"
          class="drag-selection absolute inset-y-0 bg-blue-400 opacity-50 border border-dashed border-blue-700 pointer-events-none"
          :style="getDragStyle()"
        ></div>
      </div>

      <!-- Day Actions: Copy & Delete -->
      <div class="p-2 text-center h-full flex items-center justify-center space-x-2 bg-gray-50">
        <button
          @click="openCopyModal(dayIndex)"
          title="复制到..."
          class="text-blue-600 hover:text-blue-800 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
          :disabled="!internalSchedule[dayIndex] || internalSchedule[dayIndex].length === 0"
        >
          <!-- Copy Icon -->
          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
            <path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
          </svg>
        </button>
        <button
          @click="clearDaySchedule(dayIndex)"
          title="清空当天时间段"
          class="text-red-600 hover:text-red-800 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
          :disabled="!internalSchedule[dayIndex] || internalSchedule[dayIndex].length === 0"
        >
          <!-- Trash Icon -->
          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
            <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
          </svg>
        </button>
      </div>
    </div>

    <!-- Tooltip -->
    <!-- 【修改】Tooltip -->
    <div
      ref="tooltip"
      v-show="tooltip.visible"
      class="scheduler-tooltip fixed bg-black text-white text-xs px-2 py-1 rounded shadow-lg whitespace-nowrap z-50 pointer-events-none"
      :style="{ left: `${tooltip.x}px`, top: `${tooltip.y}px` }"
    >
      {{ tooltip.text }}
    </div>

    <!-- Edit Modal -->
    <div v-if="editModal.visible" class="fixed inset-0 z-40 bg-black bg-opacity-50" @click="closeEditModal"></div>
    <div v-if="editModal.visible" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white p-6 border border-gray-300 shadow-lg min-w-[350px]">
      <h3 class="text-lg font-semibold mb-4">编辑时间段</h3>
      <div class="flex items-center mb-3 space-x-2">
        <label for="modal-start-time" class="text-sm">开始:</label>
        <input type="time" id="modal-start-time" v-model="editModal.beginTime" pattern="([01]\d|2[0-3]):([0-5]\d)|24:00" placeholder="HH:MM" title="格式: HH:MM 或 24:00" class="border border-gray-300 rounded px-2 py-1 text-sm flex-grow w-24">
        <label for="modal-end-time" class="text-sm ml-4">结束:</label>
        <input type="time" id="modal-end-time" v-model="editModal.endTime" pattern="([01]\d|2[0-3]):([0-5]\d)|24:00" placeholder="HH:MM" title="格式: HH:MM 或 24:00" class="border border-gray-300 rounded px-2 py-1 text-sm flex-grow w-24">
      </div>
      <div class="text-red-600 text-xs min-h-[1.2em] mb-3">{{ editModal.error }}</div>
      <div class="flex justify-between items-center">
        <button @click="deleteEdit" class="px-4 py-1.5 text-sm  bg-red-600 text-white hover:bg-red-700">
          删除
        </button>
        <div class="space-x-2">
          <button @click="closeEditModal" class="px-4 py-1.5 text-sm  bg-gray-200 hover:bg-gray-300 border border-gray-300">
            取消
          </button>
          <button @click="saveEdit" class="px-4 py-1.5 text-sm  bg-green-600 text-white hover:bg-green-700">
            保存
          </button>
        </div>
      </div>
    </div>


    <!-- Copy Modal -->
    <div v-if="copyModal.visible" class="fixed inset-0 z-40 bg-black bg-opacity-50" @click="closeCopyModal"></div>
    <div v-if="copyModal.visible" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white p-6 rounded-lg border border-gray-300 shadow-lg min-w-[350px]">
      <h3 class="text-lg font-semibold mb-4">复制 "{{ daysOfWeek[copyModal.sourceDayIndex] }}" 的计划到...</h3>
      <div class="mb-3 border-b pb-2">
        <label class="flex items-center text-sm font-medium cursor-pointer">
          <input type="checkbox" v-model="copyModal.selectAll" @change="toggleSelectAllTargets" class="mr-2 rounded">
          全选/全不选
        </label>
      </div>
      <div class="grid grid-cols-3 gap-2 mb-4">
        <label v-for="(dayName, index) in daysOfWeek" :key="'copy-target-'+index" class="flex items-center text-sm cursor-pointer">
          <input
            type="checkbox"
            v-model="copyModal.targetDays"
            :value="index"
            class="mr-2 rounded"
            :disabled="index === copyModal.sourceDayIndex"
            @change="updateSelectAllState"
          >
          <span :class="{'text-gray-400': index === copyModal.sourceDayIndex}">{{ dayName }}</span>
        </label>
      </div>
      <div class="text-red-600 text-xs min-h-[1.2em] mb-3">{{ copyModal.error }}</div>
      <div class="flex justify-end space-x-2">
        <button @click="closeCopyModal" class="px-4 py-1.5 text-sm rounded bg-gray-200 hover:bg-gray-300 border border-gray-300">
          取消
        </button>
        <button @click="confirmCopy" class="px-4 py-1.5 text-sm rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50" :disabled="copyModal.targetDays.length === 0">
          确认复制
        </button>
      </div>
    </div>

  </div>
</template>

<script>
const MINUTES_IN_DAY = 24 * 60;
const MIN_DURATION_MINUTES = 5;
let rangeIdCounter = 0;

export default {
  name: 'WeeklyTimeScheduler',
  props: {
    initialSchedule: {
      type: Array,
      default: () => ([]),
    }
  },
  data() {
    return {
      daysOfWeek: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
      internalSchedule: {}, // Populated by watcher
      dragging: false,
      draggingInfo: {
        dayIndex: null,
        startX: null,
        currentX: null,
        timelineWidth: null,
      },
      resizing: false,
      resizingInfo: {
        dayIndex: null,
        rangeIndex: null,
        handle: null, // 'left' or 'right'
        startX: null, // Mouse start X
        originalStartMinutes: null,
        originalEndMinutes: null,
        timelineWidth: null,
      },
      tooltip: {
        visible: false,
        text: '',
        x: 0,
        y: 0,
        // No height needed now with transform translate-y
      },
      editModal: {
        visible: false,
        dayIndex: null,
        rangeIndex: null,
        beginTime: '',
        endTime: '',
        error: '',
      },
      copyModal: {
        visible: false,
        sourceDayIndex: null,
        targetDays: [],
        selectAll: false,
        error: '',
      },
      justResizedOrDragged: false,
      form: {},

      movingBar: false,
      movingBarInfo: {
        dayIndex: null,
        rangeId: null,
        startX: null, // Mouse start clientX
        originalStartMinutes: null,
        originalEndMinutes: null,
        timelineWidth: null,
      },

    };
  },
  computed: {
    isScheduleEmpty() {
      // Check if all days in the schedule have empty arrays
      return Object.values(this.internalSchedule).every(daySchedule => !daySchedule || daySchedule.length === 0);
    }
  },
  watch: {
    initialSchedule: {
      handler(newScheduleArray) {
        const scheduleMap = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: []};
        const dayNameToIndex = { // Map input day names to internal index (0=Mon, 6=Sun)
          Monday: 0,
          Tuesday: 1,
          Wednesday: 2,
          Thursday: 3,
          Friday: 4,
          Saturday: 5,
          Sunday: 6,
        };
        rangeIdCounter = 0; // Reset internal unique ID counter

        if (Array.isArray(newScheduleArray)) {
          newScheduleArray.forEach(dayData => {
            const dayIndex = dayNameToIndex[dayData.dayOfWeek];
            if (dayIndex !== undefined && Array.isArray(dayData.timeSchedules)) {
              const dayRanges = dayData.timeSchedules
                // Sort input timeSchedules by start time before processing, helps with potential merging later
                .sort((a, b) => a.beginTime.localeCompare(b.beginTime))
                .map(action => {
                  // Extract HH:MM from HH:MM:SS for minute conversion
                  const startStr = action.beginTime.substring(0, 5);
                  const endStr = action.endTime.substring(0, 5);

                  const startMinutes = this.hhmmToMinutes(startStr);
                  const endMinutes = this.hhmmToMinutes(endStr);

                  // Basic validation for converted times
                  if (!isNaN(startMinutes) && !isNaN(endMinutes) && startMinutes < endMinutes) {
                    return {
                      start: startMinutes,
                      end: endMinutes,
                      id: `range-${rangeIdCounter++}` // Assign internal unique ID
                    };
                  }
                  return null; // Skip invalid entries
                })
                .filter(range => range !== null); // Remove nulls

              // Add valid ranges to the map, potentially replacing existing (if any)
              scheduleMap[dayIndex] = dayRanges;
            }
          });
        }

        // Create a deep copy for internal use
        this.internalSchedule = JSON.parse(JSON.stringify(scheduleMap));
        this.emitUpdate()
      },
      immediate: true,
    }
  },
  mounted() {
    document.addEventListener('mousemove', this.handleMouseMove);
    document.addEventListener('mouseup', this.handleMouseUp);
    document.addEventListener('keydown', this.handleKeyDown);
  },
  beforeDestroy() {
    document.removeEventListener('mousemove', this.handleMouseMove);
    document.removeEventListener('mouseup', this.handleMouseUp);
    document.removeEventListener('keydown', this.handleKeyDown);
  },
  methods: {
    // --- ID Management ---
    // ensureRangeIds is less critical now with the watcher handling initial IDs
    // but kept in case it's needed elsewhere, though mergeAndSort might need updating
    // if IDs weren't consistently present before watcher change.
    ensureRangeIds() {
      for (const dayIndex in this.internalSchedule) {
        this.internalSchedule[dayIndex].forEach(range => {
          if (!range.id) {
            this.$set(range, 'id', `range-${rangeIdCounter++}`);
          }
        });
      }
    },

    // --- Time & Pixel Conversion ---
    minutesToHHMM(minutes) {
      if (isNaN(minutes) || minutes < 0 || minutes > MINUTES_IN_DAY) { return "00:00"; }
      if (minutes === MINUTES_IN_DAY) return "24:00";
      const hours = Math.floor(minutes / 60);
      const mins = Math.floor(minutes % 60);
      return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
    },
    hhmmToMinutes(hhmmString) {
      if (!hhmmString) return NaN;
      if (hhmmString === "24:00") return MINUTES_IN_DAY;
      if (!/^\d{2}:\d{2}$/.test(hhmmString)) return NaN;
      const [hours, minutes] = hhmmString.split(':').map(Number);
      if (hours < 0 || hours > 24 || minutes < 0 || minutes >= 60 || (hours === 24 && minutes > 0)) return NaN;
      return hours * 60 + minutes;
    },
    pixelsToMinutes(pixels, totalWidth) {
      if (totalWidth <= 0) return 0;
      const fraction = Math.max(0, Math.min(pixels / totalWidth, 1));
      // Snap to 5-minute intervals? Optional.
      // return Math.round(fraction * MINUTES_IN_DAY / 5) * 5;
      return Math.round(fraction * MINUTES_IN_DAY);
    },
    minutesToPixels(minutes, totalWidth) {
      if (totalWidth <= 0) return 0;
      return (minutes / MINUTES_IN_DAY) * totalWidth;
    },

    // --- Styling ---
    getBarStyle(range) {
      const leftPercent = (range.start / MINUTES_IN_DAY) * 100;
      const widthPercent = ((range.end - range.start) / MINUTES_IN_DAY) * 100;
      // Add min-width visually if needed, e.g., via pixels
      // const minWidthPx = 2;
      // const minWidthPercent = (minWidthPx / (this.$refs.timelines?.[0]?.offsetWidth ?? 1000)) * 100;
      return {
        left: `${leftPercent}%`,
        width: `${Math.max(0.1, widthPercent)}%`, // Ensure minimum visual width %
        // minWidth: `${minWidthPx}px` // Alternative: use min-width pixel value
      };
    },
    getDragStyle() {
      if (!this.dragging || this.draggingInfo.timelineWidth <= 0) return { display: 'none' };
      const startPixel = Math.min(this.draggingInfo.startX, this.draggingInfo.currentX);
      const endPixel = Math.max(this.draggingInfo.startX, this.draggingInfo.currentX);
      return {
        left: `${startPixel}px`,
        width: `${Math.max(1, endPixel - startPixel)}px`, // Ensure minimum visual width 1px
      }
    },

    // --- Tooltip ---
    // 【修改】showTooltip 方法
    showTooltip(text, targetRect) {
      if (!targetRect || (targetRect.width === 0 && targetRect.height === 0)) {
        return;
      }

      this.$nextTick(() => {
        const tooltipEl = this.$refs.tooltip;
        if (!tooltipEl) return;

        // 每次显示前，先重置翻转状态
        tooltipEl.classList.remove('tooltip-flipped');
        this.tooltip.text = text;
        this.tooltip.visible = true;

        // 统一定义提示框与目标元素的间距
        const PADDING_AROUND = 8;
        let targetCenterX = targetRect.left + targetRect.width / 2;
        let targetTopY = targetRect.top - PADDING_AROUND; // 默认定位在目标元素的上方

        this.$nextTick(() => { // 等待提示框渲染完成以获取其真实尺寸
          const tooltipWidth = tooltipEl.offsetWidth;
          const tooltipHeight = tooltipEl.offsetHeight;
          const viewportXPadding = 10;
          const viewportYPadding = 10;

          // 调整 X 轴位置，防止其超出视窗左右边界
          targetCenterX = Math.max(
            viewportXPadding + tooltipWidth / 2,
            Math.min(targetCenterX, document.documentElement.clientWidth - viewportXPadding - tooltipWidth / 2)
          );

          // 检查上方空间是否足够，如果不足，则翻转到下方
          if ((targetTopY - tooltipHeight) < (window.scrollY + viewportYPadding)) {
            // 翻转到下方
            targetTopY = targetRect.bottom + PADDING_AROUND;
            tooltipEl.style.transform = 'translateX(-50%) translateY(0)';
            tooltipEl.classList.add('tooltip-flipped'); // 添加翻转类，让CSS改变箭头方向
          } else {
            // 保持在上方
            tooltipEl.style.transform = 'translateX(-50%) translateY(-100%)';
          }

          // 设置最终坐标
          this.tooltip.x = targetCenterX;
          this.tooltip.y = targetTopY;
        });
      });
    },
    hideTooltip() {
      this.tooltip.visible = false;
    },

    // --- Drag & Resize Handlers ---
    handleTimelineMouseDown(event, dayIndex) {
      if (event.button !== 0 || event.target.classList.contains('selection-bar') || event.target.classList.contains('resize-handle')) return;
      this.cancelResize(); // Cancel any ongoing resize

      const timelineElement = this.$refs.timelines[dayIndex];
      if (!timelineElement) return;
      const rect = timelineElement.getBoundingClientRect();
      const offsetX = Math.max(0, Math.min(event.clientX - rect.left, rect.width));

      this.dragging = true;
      this.draggingInfo = {
        dayIndex: dayIndex,
        startX: offsetX,
        currentX: offsetX,
        timelineWidth: timelineElement.offsetWidth,
      };

      const startMin = this.pixelsToMinutes(offsetX, this.draggingInfo.timelineWidth);
      const initialTooltipRect = {
        left: rect.left + offsetX,
        top: rect.top,
        width: 0, // Minimal width at start
        height: rect.height,
        bottom: rect.bottom,
        right: rect.left + offsetX
      };
      this.showTooltip(`${this.minutesToHHMM(startMin)} - ${this.minutesToHHMM(startMin)}`, initialTooltipRect);
    },

    handleResizeMouseDown(event, dayIndex, rangeIndex, handle) {
      if (event.button !== 0) return;
      this.cancelDrag();
      this.hideTooltip();

      const timelineElement = this.$refs.timelines[dayIndex];
      const range = this.internalSchedule[dayIndex]?.[rangeIndex];
      // Find the specific bar element using the unique data-bar-id
      const barElement = this.$refs.selectionBars?.find(el => el.dataset.barId === `${dayIndex}-${range?.id}`);

      if (!timelineElement || !range || !barElement) {
        console.error("Resize start failed: elements not found", { dayIndex, rangeIndex, handle, rangeExists: !!range, barExists: !!barElement });
        return;
      }

      this.resizing = true;
      this.resizingInfo = {
        dayIndex: dayIndex,
        rangeIndex: rangeIndex,
        rangeId: range.id, // Store ID for accurate update during move
        handle: handle,
        startX: event.clientX,
        originalStartMinutes: range.start,
        originalEndMinutes: range.end,
        timelineWidth: timelineElement.offsetWidth,
      };

      this.showTooltip(
        `${this.minutesToHHMM(range.start)} - ${this.minutesToHHMM(range.end)}`,
        barElement.getBoundingClientRect()
      );
    },

    handleDragMove(event) {
      if (!this.dragging) return;
      const { dayIndex, startX, timelineWidth } = this.draggingInfo;
      const timelineElement = this.$refs.timelines[dayIndex];
      if (!timelineElement) return;

      const rect = timelineElement.getBoundingClientRect();
      const mouseOffsetX = Math.max(0, Math.min(event.clientX - rect.left, timelineWidth));
      this.draggingInfo.currentX = mouseOffsetX; // Update currentX here

      // Use updated currentX for calculations
      const currentX = this.draggingInfo.currentX;
      const startPixel = Math.min(startX, currentX);
      const endPixel = Math.max(startX, currentX);
      const startMin = this.pixelsToMinutes(startPixel, timelineWidth);
      const endMin = this.pixelsToMinutes(endPixel, timelineWidth);

      const dragRect = {
        left: rect.left + startPixel,
        top: rect.top,
        width: endPixel - startPixel,
        height: rect.height,
        bottom: rect.bottom,
        right: rect.left + endPixel,
      };

      // Only show tooltip if duration is potentially valid (e.g., > 0)
      if (endMin > startMin) {
        this.showTooltip(`${this.minutesToHHMM(startMin)} - ${this.minutesToHHMM(endMin)}`, dragRect);
      } else {
        this.hideTooltip(); // Hide if dragging creates zero/negative duration
      }
    },

    handleResizeMove(event) {
      if (!this.resizing) return;
      const { dayIndex, rangeIndex, rangeId, handle, startX, originalStartMinutes, originalEndMinutes, timelineWidth } = this.resizingInfo;

      // Find the range and bar element *again* using the stored ID, as rangeIndex might become invalid if sorting happens mid-drag (unlikely but safer)
      const daySchedule = this.internalSchedule[dayIndex];
      const targetRangeIndex = daySchedule?.findIndex(r => r.id === rangeId);
      const targetRange = daySchedule?.[targetRangeIndex];
      const barElement = this.$refs.selectionBars?.find(el => el.dataset.barId === `${dayIndex}-${rangeId}`);
      const timelineElement = this.$refs.timelines[dayIndex]; // Find timeline element again

      if (!targetRange || !barElement || !timelineElement || timelineWidth <= 0) {
        // console.warn("Resize move skipped: elements or range not found or invalid width", { rangeId, targetRangeIndex, barElementExists: !!barElement, timelineWidth});
        return;
      }


      const currentX = event.clientX;
      const deltaX = currentX - startX;
      const deltaMinutes = Math.round((deltaX / timelineWidth) * MINUTES_IN_DAY);

      let newStart = targetRange.start; // Use current range values as base in case of rapid updates
      let newEnd = targetRange.end;

      if (handle === 'left') {
        newStart = originalStartMinutes + deltaMinutes;
        // Clamp start: Ensure it's >= 0 and doesn't cross end (maintaining min duration)
        newStart = Math.max(0, Math.min(newStart, targetRange.end - MIN_DURATION_MINUTES));
      } else { // handle === 'right'
        newEnd = originalEndMinutes + deltaMinutes;
        // Clamp end: Ensure it's <= MINUTES_IN_DAY and doesn't cross start (maintaining min duration)
        newEnd = Math.min(MINUTES_IN_DAY, Math.max(newEnd, targetRange.start + MIN_DURATION_MINUTES));
      }

      // Update the range in the internal schedule directly
      this.$set(targetRange, 'start', newStart);
      this.$set(targetRange, 'end', newEnd);

      // Update tooltip, positioning it relative to the updated bar dimensions
      this.showTooltip(
        `${this.minutesToHHMM(newStart)} - ${this.minutesToHHMM(newEnd)}`,
        barElement.getBoundingClientRect() // Get updated rect as style changes
      );
    },

    handleMouseMove(event) {
      if (this.movingBar) {
        this.handleBarMove(event);
      } else if (this.resizing) {
        this.handleResizeMove(event);
      } else if (this.dragging) {
        this.handleDragMove(event);
      }
    },

    handleMouseUp(event) {
      if (this.movingBar && event.button === 0) {
        this.handleBarMoveEnd(event); // <-- 传递 event
      } else if (this.resizing && event.button === 0) {
        this.handleResizeEnd(event);
      } else if (this.dragging && event.button === 0) {
        this.handleDragEnd(event);
      } else {
        // 如果鼠标释放不是为了结束操作，确保清除所有状态
        if(this.dragging) this.cancelDrag();
        if(this.resizing) this.cancelResize();
        if(this.movingBar) this.cancelMove();
      }
    },

    handleKeyDown(event) {
      if (event.key === 'Escape') {
        this.cancelDrag();
        this.cancelResize();
        this.cancelMove(); // <-- 添加此行
        this.closeEditModal();
        this.closeCopyModal();
      }
    },

    handleDragEnd(event) {
      if (!this.dragging) return; // Already checked button in handleMouseUp
      this.hideTooltip();
      const { dayIndex, startX, currentX, timelineWidth } = this.draggingInfo;
      this.dragging = false; // Reset state FIRST

      const startPixel = Math.min(startX, currentX);
      const endPixel = Math.max(startX, currentX);

      // Check for minimal movement and valid width
      if (Math.abs(startX - currentX) > 3 && timelineWidth > 0) {
        const startMinute = this.pixelsToMinutes(startPixel, timelineWidth);
        const endMinute = this.pixelsToMinutes(endPixel, timelineWidth);

        // Check for minimum duration
        if (endMinute > startMinute && (endMinute - startMinute) >= MIN_DURATION_MINUTES) {
          this.addScheduleEntry(dayIndex, { start: startMinute, end: endMinute });
        }
      }
      // Reset draggingInfo AFTER potential use (like addScheduleEntry)
      this.draggingInfo = { dayIndex: null, startX: null, currentX: null, timelineWidth: null };
    },

    // ... in methods object ...

    handleResizeEnd(event) {
      if (!this.resizing) return;
      this.hideTooltip();

      // *** START OF MODIFICATION ***
      // 设置一个标志位，以防止随后的 click 事件打开模态框。
      // 这模仿了拖放结束处理程序的行为。
      this.justResizedOrDragged = true;
      // 在当前事件周期完成后重置该标志。
      setTimeout(() => { this.justResizedOrDragged = false; }, 0);
      // *** END OF MODIFICATION ***

      // Find the index again based on ID, in case it changed
      const { dayIndex, rangeId } = this.resizingInfo;
      this.resizing = false; // Reset state FIRST

      const targetRangeIndex = this.internalSchedule[dayIndex]?.findIndex(r => r.id === rangeId);

      if (targetRangeIndex !== -1 && targetRangeIndex !== undefined) {
        const range = this.internalSchedule[dayIndex][targetRangeIndex];
        // Optional: Final validation or snapping
        if (range.end - range.start < MIN_DURATION_MINUTES) {
          // If somehow the final state is invalid, revert or adjust?
          // For now, we assume move logic prevents this.
          console.warn("Resize ended with invalid duration", range);
        }

        // Merge and sort needed after resize potentially changes order or creates overlaps
        this.$nextTick(() => {
          this.mergeAndSortDaySchedule(dayIndex);
          this.emitUpdate();
        });
      } else {
        console.error("Could not find range to finalize resize for ID:", rangeId);
        // May still need to emit update if other ranges were affected?
        this.emitUpdate();
      }

      // Reset resizingInfo AFTER potential use
      this.resizingInfo = { dayIndex: null, rangeIndex: null, rangeId: null, handle: null, startX: null, originalStartMinutes: null, originalEndMinutes: null, timelineWidth: null };
    },

    // --- Cancel Actions ---
    cancelDrag() {
      if(this.dragging) {
        this.dragging = false;
        this.draggingInfo = { dayIndex: null, startX: null, currentX: null, timelineWidth: null };
        this.hideTooltip();
      }
    },
    cancelResize() {
      if (this.resizing) {
        // If cancelling resize, *revert* the change before resetting state
        const { dayIndex, rangeId, originalStartMinutes, originalEndMinutes } = this.resizingInfo;
        const targetRangeIndex = this.internalSchedule[dayIndex]?.findIndex(r => r.id === rangeId);
        if(targetRangeIndex !== -1 && targetRangeIndex !== undefined) {
          const targetRange = this.internalSchedule[dayIndex][targetRangeIndex];
          this.$set(targetRange, 'start', originalStartMinutes);
          this.$set(targetRange, 'end', originalEndMinutes);
        }

        this.resizing = false;
        this.resizingInfo = { dayIndex: null, rangeIndex: null, rangeId: null, handle: null, startX: null, originalStartMinutes: null, originalEndMinutes: null, timelineWidth: null };
        this.hideTooltip();
        // No emitUpdate needed on cancel-revert unless merge/sort is required
      }
    },
    // --- Bar Interaction ---
    handleBarMouseOver(event, dayIndex, rangeIndex) {
      if (this.dragging || this.resizing || this.editModal.visible) return;

      // Find range by index *safely*
      const range = this.internalSchedule[dayIndex]?.[rangeIndex];
      // Get the bar element itself reliably
      const barElement = event.target.closest('.selection-bar');

      if(range && barElement) {
        const timeText = `${this.minutesToHHMM(range.start)} - ${this.minutesToHHMM(range.end)}`;
        this.showTooltip(timeText, barElement.getBoundingClientRect());
      } else {
        // console.warn("MouseOver failed to find range or element", {dayIndex, rangeIndex, rangeExists: !!range, barElementExists: !!barElement});
      }
    },

    handleBarClick(event, dayIndex, rangeIndex) {
      if (this.justResizedOrDragged || this.movingBar) { // <-- 增加 this.movingBar 检查
        return;
      }
      if (this.dragging || this.resizing) return;

      // Find range by index *safely* before opening modal
      const range = this.internalSchedule[dayIndex]?.[rangeIndex];
      if (!range) {
        console.error("Clicked bar corresponds to a non-existent range", {dayIndex, rangeIndex});
        return;
      }

      this.openEditModal(dayIndex, rangeIndex);
    },

    handleBarMouseDown(event, dayIndex, rangeIndex) {
      // 仅在主鼠标按钮按下且未点击到缩放手柄时触发
      if (event.button !== 0 || event.target.classList.contains('resize-handle')) {
        return;
      }
      // 取消其他可能正在进行的操作
      this.cancelDrag();
      this.cancelResize();

      const timelineElement = this.$refs.timelines[dayIndex];
      const range = this.internalSchedule[dayIndex]?.[rangeIndex];

      if (!timelineElement || !range) {
        console.error("Bar move start failed: elements not found");
        return;
      }

      // 设置拖动状态
      this.movingBar = true;
      this.movingBarInfo = {
        dayIndex: dayIndex,
        rangeId: range.id,
        startX: event.clientX,
        originalStartMinutes: range.start,
        originalEndMinutes: range.end,
        timelineWidth: timelineElement.offsetWidth,
      };

      // 更改鼠标样式以提示用户
      document.body.style.cursor = 'grabbing';
    },

    handleBarMove(event) {
      if (!this.movingBar) return;

      const { dayIndex, rangeId, startX, originalStartMinutes, originalEndMinutes, timelineWidth } = this.movingBarInfo;

      // 使用ID安全地找到目标范围
      const targetRange = this.internalSchedule[dayIndex]?.find(r => r.id === rangeId);
      const barElement = this.$refs.selectionBars?.find(el => el.dataset.barId === `${dayIndex}-${rangeId}`);

      if (!targetRange || !barElement || timelineWidth <= 0) return;

      const deltaX = event.clientX - startX;
      const deltaMinutes = Math.round((deltaX / timelineWidth) * MINUTES_IN_DAY);
      const duration = originalEndMinutes - originalStartMinutes;

      // 计算新的开始时间，并限制在 0 和 (总分钟数 - 持续时间) 之间
      let newStart = originalStartMinutes + deltaMinutes;
      newStart = Math.max(0, Math.min(newStart, MINUTES_IN_DAY - duration));
      const newEnd = newStart + duration;

      // 更新数据
      this.$set(targetRange, 'start', newStart);
      this.$set(targetRange, 'end', newEnd);

      // 更新工具提示
      this.showTooltip(
        `${this.minutesToHHMM(newStart)} - ${this.minutesToHHMM(newEnd)}`,
        barElement.getBoundingClientRect()
      );
    },

    handleBarMoveEnd(event) {
      if (!this.movingBar) return;

      this.hideTooltip();
      document.body.style.cursor = ''; // 总是恢复鼠标样式

      // 计算鼠标移动的距离
      const deltaX = Math.abs(event.clientX - this.movingBarInfo.startX);
      const wasDrag = deltaX > 3; // 设置一个3像素的阈值来区分点击和拖拽

      const dayIndex = this.movingBarInfo.dayIndex; // 在重置状态前获取信息

      // **非常重要**: 先重置状态，再执行后续逻辑
      this.movingBar = false;

      if (wasDrag) {
        // 这是一次真正的拖拽
        this.justResizedOrDragged = true;
        setTimeout(() => { this.justResizedOrDragged = false; }, 0);

        // 拖动结束后，时间块可能与其它块重叠，需要进行合并排序
        this.$nextTick(() => {
          this.mergeAndSortDaySchedule(dayIndex);
          this.emitUpdate();
        });
      } else {
        // 这是一次点击，不是拖拽。我们什么都不做。
        // `justResizedOrDragged` 标志位为 false，
        // 接下来的 `click` 事件将能够正常触发 `handleBarClick` 打开模态框。

        // 如果因为微小移动导致时间块位置变化，可以把它恢复原位
        const { rangeId, originalStartMinutes, originalEndMinutes } = this.movingBarInfo;
        const targetRange = this.internalSchedule[dayIndex]?.find(r => r.id === rangeId);
        if(targetRange) {
          this.$set(targetRange, 'start', originalStartMinutes);
          this.$set(targetRange, 'end', originalEndMinutes);
        }
      }
    },

    cancelMove() {
      if (this.movingBar) {
        // 如果取消，则恢复到原始位置
        const { dayIndex, rangeId, originalStartMinutes, originalEndMinutes } = this.movingBarInfo;
        const targetRange = this.internalSchedule[dayIndex]?.find(r => r.id === rangeId);
        if(targetRange) {
          this.$set(targetRange, 'start', originalStartMinutes);
          this.$set(targetRange, 'end', originalEndMinutes);
        }
        this.movingBar = false;
        this.hideTooltip();
        document.body.style.cursor = '';
      }
    },

    // --- Data Management ---
    addScheduleEntry(dayIndex, newEntry) {
      if (!this.internalSchedule[dayIndex]) {
        this.$set(this.internalSchedule, dayIndex, []);
      }
      // Assign a unique ID upon creation
      const entryWithId = { ...newEntry, id: `range-${rangeIdCounter++}` };

      // Add the new entry
      this.internalSchedule[dayIndex].push(entryWithId);

      // Merge and sort AFTER adding
      this.mergeAndSortDaySchedule(dayIndex); // This will handle overlaps with existing ones
      this.emitUpdate();
    },

    mergeAndSortDaySchedule(dayIndex) {
      const daySchedule = this.internalSchedule[dayIndex];
      if (!daySchedule || daySchedule.length === 0) return;

      // Ensure only valid ranges are processed
      const validSchedule = daySchedule.filter(r => r && typeof r.start === 'number' && typeof r.end === 'number' && r.start < r.end);
      if (validSchedule.length === 0) {
        this.$set(this.internalSchedule, dayIndex, []); // Clear if no valid ranges left
        return;
      }
      if (validSchedule.length < 2) {
        // If only 0 or 1 valid range, no merge needed, just ensure the array contains only it.
        this.$set(this.internalSchedule, dayIndex, validSchedule);
        return;
      }

      // Sort by start time
      validSchedule.sort((a, b) => a.start - b.start);

      const merged = [];
      // Keep the first range (make sure to preserve its ID)
      merged.push({ ...validSchedule[0] });

      for (let i = 1; i < validSchedule.length; i++) {
        const lastMerged = merged[merged.length - 1];
        const current = validSchedule[i];

        // Check for overlap or adjacency (<= allows adjacent blocks like 08:00-09:00 and 09:00-10:00 to merge)
        // Use a small tolerance if needed? e.g., current.start <= lastMerged.end + 1
        if (current.start <= lastMerged.end) {
          // Merge: Extend the end time of the last merged block
          lastMerged.end = Math.max(lastMerged.end, current.end);
          // Note: The ID of the merged block remains the ID of the *first* range in the merged sequence.
        } else {
          // No overlap, add the current range as a new block (preserving its ID)
          merged.push({ ...current });
        }
      }
      // Replace the entire day's schedule with the merged result
      this.$set(this.internalSchedule, dayIndex, merged);
    },

    // NEW helper function for HH:MM:SS format
    minutesToHHMMSS(minutes) {
      if (isNaN(minutes) || minutes < 0 || minutes > MINUTES_IN_DAY) {
        return "00:00:00"; // Default or error case
      }
      if (minutes === MINUTES_IN_DAY) {
        return "24:00:00"; // Handle end of day special case
      }
      const hours = Math.floor(minutes / 60);
      const mins = Math.floor(minutes % 60);
      // Seconds are always 00 as the scheduler uses minute precision
      return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;
    },

    // REVISED emitUpdate method
    emitUpdate() {
      // Mapping from internal index (0-6) to English Day Name
      // Assuming 0 = Monday based on ['周一', '周二', ...]
      const dayMap = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
      const scheduleAction = [];

      // Ensure the schedule is sorted before processing for emission
      // (Merge/sort should handle this, but double-check doesn't hurt)
      for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
        if (this.internalSchedule[dayIndex]?.length > 1) {
          this.internalSchedule[dayIndex].sort((a, b) => a.start - b.start);
        }
      }


      for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
        const dayRanges = this.internalSchedule[dayIndex] || [];
        const dayName = dayMap[dayIndex];

        // Limit to max 8 ranges and format them
        const actionArray = dayRanges
          .slice(0, 8) // Take only the first 8 ranges
          .map((range, index) => {
            // Ensure range has valid start/end before formatting
            if (typeof range.start === 'number' && typeof range.end === 'number') {
              return {
                id: index + 1, // Sequential ID from 1 to 8
                beginTime: this.minutesToHHMMSS(range.start),
                endTime: this.minutesToHHMMSS(range.end),
              };
            }
            return null; // Skip invalid ranges if any slipped through
          })
          .filter(action => action !== null); // Remove any nulls from invalid ranges

        // Add the day's schedule to the final array
        scheduleAction.push({
          dayOfWeek: dayName,
          timeSchedules: actionArray,
        });
      }
      const finalOutput = { scheduleAction: scheduleAction };

      console.log("Emitting schedule update:", finalOutput);
      this.form = finalOutput
    },

    // --- NEW: Clear Schedule Methods ---
    clearDaySchedule(dayIndex) {
      if (this.internalSchedule[dayIndex] && this.internalSchedule[dayIndex].length > 0) {
        this.$set(this.internalSchedule, dayIndex, []);
        this.emitUpdate();
        this.$nextTick(() => {
        });
      }
    },

    clearAllSchedule() {
      // Add confirmation
      // if (this.$confirm("确定要清空所有时间段吗？此操作无法撤销。")) {
      this.$confirm("确定要清空所有时间段吗？", '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        for (let i = 0; i < 7; i++) {
          if (this.internalSchedule[i] && this.internalSchedule[i].length > 0) {
            this.$set(this.internalSchedule, i, []);
          }
        }
        this.emitUpdate();
      })
    },
    // --- Edit Modal ---
    openEditModal(dayIndex, rangeIndex) {
      const range = this.internalSchedule[dayIndex]?.[rangeIndex];
      if (!range) {
        console.error("Cannot open edit modal, range not found at index:", rangeIndex);
        return;
      }
      this.hideTooltip();
      this.editModal = {
        visible: true,
        dayIndex: dayIndex,
        rangeIndex: rangeIndex, // Keep index for splice/update logic for now
        rangeId: range.id,      // Store ID for safer updates
        beginTime: this.minutesToHHMM(range.start),
        endTime: this.minutesToHHMM(range.end),
        error: '',
      };
    },
    closeEditModal() {
      this.editModal.visible = false;
      this.editModal.error = ''; // Clear error on close
    },
    saveEdit() {
      this.editModal.error = '';
      let startMinutes = this.hhmmToMinutes(this.editModal.beginTime);
      let endMinutes = this.hhmmToMinutes(this.editModal.endTime);

      // Validation
      if (isNaN(startMinutes) || isNaN(endMinutes)) { this.editModal.error = '时间格式无效 (HH:MM 或 24:00)'; return; }
      if (startMinutes >= endMinutes) { this.editModal.error = '结束时间必须晚于开始时间'; return; }
      if ((endMinutes - startMinutes) < MIN_DURATION_MINUTES) { this.editModal.error = `最短持续时间为 ${MIN_DURATION_MINUTES} 分钟`; return; }

      // Find the range using ID for safety, fallback to index
      const daySchedule = this.internalSchedule[this.editModal.dayIndex];
      let targetRangeIndex = this.editModal.rangeIndex; // Use original index as fallback
      if (this.editModal.rangeId) {
        const foundIndex = daySchedule?.findIndex(r => r.id === this.editModal.rangeId);
        if (foundIndex !== -1 && foundIndex !== undefined) {
          targetRangeIndex = foundIndex;
        } else {
          console.warn("Range ID mismatch during save, falling back to index. ID:", this.editModal.rangeId);
        }
      }

      const targetRange = daySchedule?.[targetRangeIndex];

      if (targetRange) {
        // Use $set for reactivity on object properties
        this.$set(targetRange, 'start', startMinutes);
        this.$set(targetRange, 'end', endMinutes);
      } else {
        console.error("Could not find range to update at index:", targetRangeIndex);
        this.editModal.error = "保存失败：找不到目标时间段。"; // User-facing error
        return; // Don't close modal if save failed internally
      }

      // Merge and sort after editing, as the time change might cause overlaps/reordering
      this.mergeAndSortDaySchedule(this.editModal.dayIndex);
      this.emitUpdate();
      this.closeEditModal();
    },
    deleteEdit() {
      if (this.editModal.dayIndex !== null) {
        // Find the range using ID for safety, fallback to index
        const daySchedule = this.internalSchedule[this.editModal.dayIndex];
        let targetRangeIndex = this.editModal.rangeIndex; // Use original index as fallback
        if (this.editModal.rangeId) {
          const foundIndex = daySchedule?.findIndex(r => r.id === this.editModal.rangeId);
          if (foundIndex !== -1 && foundIndex !== undefined) {
            targetRangeIndex = foundIndex;
          } else {
            console.warn("Range ID mismatch during delete, falling back to index. ID:", this.editModal.rangeId);
          }
        }

        if (targetRangeIndex !== null && targetRangeIndex >= 0 && targetRangeIndex < daySchedule.length) {
          daySchedule.splice(targetRangeIndex, 1); // Remove the element
          // No need to merge/sort after simple deletion
          this.emitUpdate();
          this.closeEditModal();
        } else {
          console.error("Could not find range to delete at index:", targetRangeIndex);
          this.editModal.error = "删除失败：找不到目标时间段。"; // User-facing error
          // Optionally close modal anyway? Or keep open? Keeping open might be confusing.
          this.closeEditModal();
        }
      }
    },

    // --- Copy Modal ---
    openCopyModal(sourceDayIndex) {
      if (!this.internalSchedule[sourceDayIndex] || this.internalSchedule[sourceDayIndex].length === 0) return;
      this.copyModal = {
        visible: true,
        sourceDayIndex: sourceDayIndex,
        targetDays: [],
        selectAll: false,
        error: '',
      }
    },
    closeCopyModal() {
      this.copyModal.visible = false;
      this.copyModal.error = ''; // Clear error on close
    },
    toggleSelectAllTargets() {
      const sourceIndex = this.copyModal.sourceDayIndex;
      if (this.copyModal.selectAll) {
        // Select all *other* days
        this.copyModal.targetDays = this.daysOfWeek
          .map((_, index) => index)
          .filter(index => index !== sourceIndex);
      } else {
        this.copyModal.targetDays = [];
      }
    },
    updateSelectAllState() {
      const sourceIndex = this.copyModal.sourceDayIndex;
      const selectableTargetCount = this.daysOfWeek.length - 1; // Total days minus source day
      // Check if the number of selected targets equals the total possible targets
      const allTargetsSelected = this.copyModal.targetDays.length === selectableTargetCount;
      // Ensure the source day isn't accidentally included in targetDays if logic changes
      const sourceNotInTargets = !this.copyModal.targetDays.includes(sourceIndex);

      this.copyModal.selectAll = allTargetsSelected && sourceNotInTargets;
    },
    confirmCopy() {
      this.copyModal.error = '';
      if (this.copyModal.targetDays.length === 0) {
        this.copyModal.error = "请至少选择一个目标日期";
        return;
      }

      const sourceScheduleRanges = this.internalSchedule[this.copyModal.sourceDayIndex];

      // Deep copy and assign NEW unique IDs to copied ranges
      const rangesToCopy = JSON.parse(JSON.stringify(sourceScheduleRanges))
        .map(range => ({
          ...range,
          id: `range-${rangeIdCounter++}` // Assign new ID
        }));

      this.copyModal.targetDays.forEach(targetDayIndex => {
        // Replace the entire schedule for the target day with the newly copied ranges
        // We use $set to ensure reactivity when replacing the whole array
        this.$set(this.internalSchedule, targetDayIndex, JSON.parse(JSON.stringify(rangesToCopy))); // Deep copy again for each target day
      });

      this.emitUpdate();
      this.closeCopyModal();
    }
  }
};
</script>

<style scoped>
.resize-handle {
  z-index: 10; /* Make sure handles are above the main bar for clicks */
}
* {
  font-size: 12px;
}
/* 【新增】提示框箭头样式 */
.scheduler-tooltip::after {
  content: '';
  position: absolute;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 6px; /* 控制箭头的大小 */
  left: 50%;
  transform: translateX(-50%);
}

/* 默认箭头（向下指），当提示框在目标上方时使用 */
.scheduler-tooltip:not(.tooltip-flipped)::after {
  top: 100%; /* 将箭头定位在提示框的底部 */
  border-color: black transparent transparent transparent; /* 只有上边框有颜色，形成一个向下的三角形 */
}

/* 翻转后的箭头（向上指），当提示框在目标下方时使用 */
.scheduler-tooltip.tooltip-flipped::after {
  bottom: 100%; /* 将箭头定位在提示框的顶部 */
  border-color: transparent transparent black transparent; /* 只有下边框有颜色，形成一个向上的三角形 */
}
</style>
